// nth-graph jquery plugin
// author: Nate Morse copyright 2009
// BSD license
// version: 0.03
// project site: http://code.google.com/p/nth-graph/
// source repository:  
// http://nth-graph.googlecode.com/svn/trunk/nth-graph/interpreters/
// usage: 
//   $("#myElement").nthGraph({options}); // runs Hello World
// options: 
//   input:{key:anyType, ...} keys are matched to node names and 
//       data is stored under the same name. Can be over written with metadata.
//   loggin:{status:off|on, dome_element_id:stringType}
//   cg:object Control Graph in JSON or JSOL
//   cgref:stringType  Url of a Control Graph in JSON 
//     (either a cg or a cgref must be included)
//   warn:function calls a function when a warning is encoutered default:console
//   debug:function called for debuging

(function($) {
	var config = {"input":{}, 
			"warn":function(msg){alert(msg);},
			"debug":function(msg){}
	};

	var nth = {
		graph:{},
		control_graph: {
			run: function($this) {
				if (config.cg) {$.extend(true, nth.graph, config.cg);}
				var input = $this.metadata ? $.extend(true, {}, config.input, $this.metadata({type:"attr", name:"nth-graph-input"})) : config.input;
				nth.debug("start run...");
				nth.attachEnv();
				nth.control_graph.traverse();
				nth.detachEnv();
			},
			traverse: function() {
				var node_index = 0;
				var traverse_limit = (config.traverse_limit)? config.traverse_limit:5000;
				while(traverse_limit > 0 && node_index >= 0) {
					traverse_limit--;
					nth.mergeNodeData(node_index, nth.control_graph.get(node_index));
					nth.control_graph.process(node_index);
					nth.control_graph.set(node_index);
					node_index = nth.control_graph.transition(node_index);
				}
			},
			get: function(node_index) {
				var data = [];
				var outEdges = nth.getOutEdges(node_index, "get");
				$.each(outEdges, function(edge_index, edge_obj) {
					var edge_name = edge_obj.name;
					var edge_alias = edge_obj.alias;
					var to_node_index = edge_obj.to;
					nth.getExternal(to_node_index);
					var new_data;
					if (!edge_name && !edge_alias) {
						new_data = nth.getNodeData(to_node_index);
						data = $.extend(true, data, new_data);
					}
					else { 
						if (!edge_name) {
							edge_name = nth.getNodeAttr(to_node_index, 'name');
						}
						if (!edge_alias) { 
							edge_alias = edge_name;
						}
						new_data = nth.getNodeDataAttr(to_node_index, edge_alias);
						nth.debug("get_data "+to_node_index+" "+edge_alias+":"+new_data);
						data[edge_name] = new_data;
					}
				});
				return data;
			},
			process: function(node_index) {
				// process a QueryGraph
				if(nth.getNodeAttr(node_index, "type") == "queryGraph"){
					//var name = nth.getNodeAttr(node_index, "name");
					//var qg = QueryGraph(name+".qg.json");
					//nth.graph.nodes[node_index].data[name] = qg.query(nth.graph.nodes[node_index].data);
				}
				var node_process = nth.getNodeProcess(node_index);
				if (node_process && typeof node_process == 'string') {
					//  TODO sanitize node_process string to adhere to "function(){}" only!
					// nth.warn("node_process does not adhere to function(){...} --instead: " + node_process);
					nth.debug(" *node process string = " + node_process)
					eval("node_process = "+node_process + ";");
				}
				if (node_process && typeof node_process == 'function') {
					node_process.call(nth.graph.nodes[node_index].data);
				}
			},
			set: function(node_index) {
				var outEdges = nth.getOutEdges(node_index, "set");
				$.each(outEdges, function(edge_index, edge_obj) {
					var from_node_index = edge_obj.from;
					var edge_name = edge_obj.name;
					var edge_alias = edge_obj.alias;
					var to_node_index = edge_obj.to;
					if (!edge_name) {
						edge_name = nth.getNodeAttr(to_node_index, 'name');
					}
					if (!edge_alias) { 
						edge_alias = edge_name;
					}
					var value = nth.getNodeDataAttr(from_node_index, edge_name);
					nth.setNodeDataAttr(to_node_index, edge_alias, value);
					var to_type = nth.getNodeAttr(to_node_index, "type");
					if (to_type == "out" || to_type == "io") {
						var output = nth.getNodeAttr(to_node_index, "output");
						if (output && output.change == "immediately") {
							var element = nth.getNodeAttr(to_node_index, "ele");
							if (output.type == "text") {
								$(element).text(value);
							}
							if (output.type == "value") {
								$(element).val(value);
							}
						}
					}
				});
			},
			transition: function(node_index) {
				var to_node_index = -1;
				var outEdges = nth.getOutEdges(node_index, "trans");
				$.each(outEdges, function(edge_index, edge_obj) {
					to_node_index = edge_obj.to;
				});
				return to_node_index;
			},
			renderOutputs: function($this) {
				$.each(nth.graph.nodes, function(node_index, node_obj) {
					if (nth.key_exists('type', node_obj) && node_obj.type == 'out' && node_obj.type == 'io') {
						var node_ele = node_obj.ele;
						if (node_ele.indexOf('#')>=0) {
							node_ele = node_ele.substr(node_ele.indexOf('#')+1);
							$(node_obj.ele).text(nth.graph.nodes[node_index].data[node_ele]);
						}
 						if (node_obj.data && node_obj.data.msg) {
							$this.text(node_obj.data.msg);
						}
					}
				});
			}
		},
		warn: function(message) {
			config.warn.call(null, message);
			//alert("warn:"+message);
		},
		debug: function(message) {
			config.debug.call(null, message);
			//alert("debug:"+message);
		},
	
		loadControlGraph: function (url) {
			// method getJSON
			$.ajax({
					"async": false,
					"url":url, "dataType":"json",
					"success": function(g){
							if (!(g)) {
								nth.warn("failed to decode graph resource or failed to open graph resource "+url);
								return false;
							}
							config.cg = g;
						},
						"error": function (XMLHttpRequest, textStatus, errorThrown) {
						  alert('error:: '+textStatus + errorThrown);
						}
			});
		},
	
		getOutEdges: function(node_index, type) {
			var outEdges = [];
			$.each(nth.graph.edges, function(edge_index, edge_obj) {
				if (edge_obj.from == node_index && edge_obj.type == type) {
					outEdges.push(nth.graph.edges[edge_index]);
				} 
			});
			return outEdges;
		},
		
		getNodeProcess: function(node_index) {
			if (!nth.key_exists(node_index, nth.graph.nodes)) {
				return 'Missing Node '+node_index;}
			if (!nth.key_exists('process', nth.graph.nodes[node_index])) {
				return false;}
			var code = nth.graph.nodes[node_index].process;
			return code;
		},
		
		getNodeAttr: function(node_index, attr) {
			if (!nth.key_exists(node_index, nth.graph.nodes)) {
				return 'none 1';}
			if (!nth.key_exists(attr, nth.graph.nodes[node_index])) {
				return 'none 2';}
			var dataSet = nth.graph.nodes[node_index][attr];
			return dataSet;
		},
		
		getNodeData: function(node_index) {
			if (!nth.key_exists(node_index, nth.graph.nodes)) {
				return 'none 1';}
			if (!nth.key_exists('data', nth.graph.nodes[node_index])) {
				return 'none 2';}
			var dataSet = nth.graph.nodes[node_index].data;
			return dataSet;
		},
		
		getNodeDataAttr: function(node_index, attr) {
			var dataSet = nth.getNodeData(node_index);
			if (!dataSet || !nth.key_exists(attr, dataSet)) {
				return 'none 3';}
			return dataSet[attr];
		},
		
		setNodeDataAttr: function(node_index, attr, value) {
			if (!nth.key_exists("data", nth.graph.nodes[node_index])) {
				nth.graph.nodes[node_index].data = {};
			}
			nth.graph.nodes[node_index].data[attr] = value;
		},
		
		attachEnv: function() {
			$.each(nth.graph.nodes, function(node_index, node_obj) {
				if (nth.key_exists("type", node_obj) && (node_obj.type == "in" || node_obj.type == "io")) {
					if (nth.key_exists("input", node_obj) && nth.key_exists("ele", node_obj)) {
						$.each(node_obj.input, function(input_event, action) {
							if (action == "traverse") {
								$(node_obj.ele).bind(input_event, nth.control_graph.traverse);
							}
						});
					}
				} 
			});
		},
		detachEnv: function() {
			// unbind here??
		},
		getExternal: function(node_index) {
			var node_obj = nth.graph.nodes[node_index];
			if (nth.key_exists('type', node_obj) && node_obj.type == 'in') {
				if (nth.key_exists('subtype', node_obj) && 
					node_obj.subtype == 'GET'){
					// $.get from url and then on success ?? should we block async=false ??
					$.getJSON(node_obj.url, {"name":"node_obj.name"}, function(json){
						if (!(json)) {
							alert("failed to get input resource "+node_obj.url);
						}
						nth.debug("opening "+node_obj.url);
						$.extend(true, nth.graph.nodes[node_index].data, json.data);
					});
				}
				else if (nth.key_exists('subtype', node_obj) && node_obj.subtype == 'POST') {
					// $.post to url and then on success
				}
				else {
					// input is from a graph input or config.input, or a jQuery/DOM source.
					//if (nth.key_exists(node_obj.name, input)) {
					//	nth.graph.nodes[node_index].data[node_obj.name] = input[node_obj.name];
					//}
					//else 
					if (config.input && nth.key_exists(node_obj.name, config.input)) {
						nth.graph.nodes[node_index].data[node_obj.name] = config.input[node_obj.name];
					}
					else 
					if (nth.key_exists('ele', node_obj)) {
					
						if (!nth.graph.nodes[node_index].data) {nth.graph.nodes[node_index].data = {};}
						var node_ele = node_obj.ele;
						if (node_ele.indexOf('#')>=0) {
							node_ele = node_ele.substr(node_ele.indexOf('#')+1);
						}
						if (nth.key_exists('subtype', node_obj) && node_obj.subtype == "checkbox") {
							nth.graph.nodes[node_index].data[node_ele] = $(node_obj.ele).is(':checked');
						} else {
							nth.graph.nodes[node_index].data[node_ele] = $(node_obj.ele).val();
						}
					}
				}
			}
		}
		,
		
		mergeNodeData: function(node_index, data) {
			//nth.graph.nodes[node_index].data = 
				nth.graph.nodes[node_index].data = $.extend(true, 
					nth.graph.nodes[node_index].data,
					data
				);
		},
				
		key_exists: function(key_str, obj) {
			if(typeof obj != "undefined" &&  typeof obj[key_str] != "undefined") {
				return true;
			}
			return false;
		}
	};

	$.fn.nthGraph = function(settings) {
		
		if (settings) {$.extend(true, config, settings);}
		if (config.cg && typeof config.cg == 'object') {$.extend(true, nth.graph, config.cg);}
		else if (config.cgref && typeof config.cgref == 'string') {
			nth.loadControlGraph(config.cgref);
		}
		else {nth.warn("No control graph (cg) indicated.");}
		
		// so far this is only tested on one jQ element but it could be used on an array,
		// A run will use metadata (if any) as input. Output will be applied back to the element. 
		this.each(function() {
			var $this = $(this);
			nth.control_graph.run($this);
		});
		
		return this;
	};
})(jQuery);

